﻿using System;
using System.Collections.Generic;
 
using System.Text;
using System.Security.Cryptography;
using System.Net;

namespace GGSharp.Packets
{
    public enum HashType : byte
    {
        GG32 = 0x01,
        SHA1 = 0x02
    }

    public class LoginPacket70 : HeaderPacket
    {
        int _uin;              /* mój numerek */
        byte _hashType = (byte)HashType.SHA1;       /* rodzaj funkcji skrótu hasła */
        byte[] _hash;        /* skrót hasła */
        int _status = (int)GGShowType.Available;           /* status na dzień dobry */
        int _version = (int)ClientVersion.v7_7_0_3315;          /* moja wersja klienta */
        byte _unknown1 = 0x00;        /* 0x00 */
        IPAddress _localIP = new IPAddress(0);         /* mój adres ip */
        short _localPort = 0;     /* port, na którym słucham */
        IPAddress _externalIP = new IPAddress(0);      /* zewnętrzny adres ip */
        short _externalPort = 0;  /* zewnętrzny port */
        byte _imageSize = 255;      /* maksymalny rozmiar grafiki w KB */
        byte _unknown2 = 0xbe;        /* 0xbe */
        string _description;   /* opis, nie musi wystąpić */
        int _time = (int)DateTime.Now.ToBinary();             /* czas, nie musi wystąpić */

        private string _password;
        private uint _seed;

        public LoginPacket70(int uin, string password, uint seed)
        {
            this.Type = PacketType.GG_LOGIN70;
            this.Length = 92;

            _uin = uin;
            _password = password;
            _seed = seed;
        }

        public ClientVersion ClientVersion
        {
            get { return TranslateClientVersion(_version); }
            set { _version = (int)value; }
        }

        public int ClientVersionRaw
        {
            get { return _version; }
            set { _version = value; }
        }

        public string Description
        {
            get { return _description; }
            set { _description = value; }
        }

        public IPAddress ExternalIPAddress
        {
            get { return _externalIP; }
            set { _externalIP = value; }
        }

        public short ExternalPort
        {
            get { return _externalPort; }
            set { _externalPort = value; }
        }

        public IPAddress LocalIPAddress
        {
            get { return _localIP; }
            set { _localIP = value; }
        }

        public short LocalPort
        {
            get { return _localPort; }
            set { _localPort = value; }
        }

        public GGShowType Show
        {
            get { return TranslateShow(_status); }
            set { _status = (int)value; }
        }

        public int ShowRaw
        {
            get { return _status; }
            set { _status = value; }
        }

        public override void Write(System.IO.BinaryWriter writer)
        {
            if (this.Description != null)
                this.Length += this.Description.Length + 1;

            base.Write(writer);

            if (_hashType == (byte)HashType.GG32)
                _hash = GetGG32Hash(_password, _seed);
            else if (_hashType == (byte)HashType.SHA1)
                _hash = GetSHA1Hash(_password, _seed);
            else
                throw new Exception("Unknown login hash algoritm");

            writer.Write(_uin);
            writer.Write((byte)_hashType);
            writer.Write(_hash);

            for (int i = 0; i < 64 - _hash.Length; i++)
                writer.Write((byte)0);

            writer.Write(_status);
            writer.Write(_version);
            writer.Write(_unknown1);
            writer.Write((int)LocalIPAddress.Address);
            writer.Write(_localPort);
            writer.Write((int)ExternalIPAddress.Address);
            writer.Write(_externalPort);
            writer.Write(_imageSize);
            writer.Write(_unknown2);
            if (_description != null)
            {
                WriteCP1250Text(_description, writer);
                //writer.Write(_time);
                //writer.Write((byte)31);
            }
        }

        private byte[] GetGG32Hash(String password, uint seed)
        {
            uint x, y, z;

            y = seed;

            int p = 0;
            for (x = 0; p < password.Length; p++)
            {
                x = (x & 0xffffff00) | Convert.ToByte(password[p]);
                y ^= x;
                y += x;
                x <<= 8;
                y ^= x;
                x <<= 8;
                y -= x;
                x <<= 8;
                y ^= x;

                z = y & 0x1f;
                y = (uint)((uint)y << (int)z) | (uint)((uint)y >> (int)(32 - z));
            }
            return BitConverter.GetBytes(y);
        }

        private byte[] GetSHA1Hash(string password, uint seed)
        {
            var passwordBytes = Encoding.UTF8.GetBytes(password);
            var seedBytes = BitConverter.GetBytes(seed);

            //create one byte array
            var bytes = new byte[passwordBytes.Length + seedBytes.Length];
            passwordBytes.CopyTo(bytes, 0);
            seedBytes.CopyTo(bytes, passwordBytes.Length);

            var sha1Hash = SHA1Managed.Create();
            return sha1Hash.ComputeHash(bytes);
        }
    }
}
